package airacrobats.gear;

import airacrobats.control.AutomaticController;
import airacrobats.control.Controller;
import airacrobats.control.PlayerController;
import airacrobats.game.Camera;
import airacrobats.game.Smoker;
import airacrobats.gui.container.GameContainer;
import airacrobats.game.object.Airplane;
import airacrobats.game.object.GameObject;
import airacrobats.game.Squadron;
import airacrobats.game.Stage;
import airacrobats.game.object.Sky;
import airacrobats.game.object.Terrain;
import airacrobats.gui.component.ALabel;
import airacrobats.io.data.FlapsData;
import airacrobats.io.data.GhostData;
import airacrobats.io.data.JWavefrontModel;
import airacrobats.io.data.MovementData;
import airacrobats.io.data.RadioData;
import airacrobats.io.data.RouteData;
import airacrobats.sound.RadioSound;
import airacrobats.sound.Sound3D;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLProfile;
import javax.sound.sampled.LineUnavailableException;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

/**
 * This is the game gear, where the game runs.
 */
public class GameGear extends Gear implements ActionListener
{
    /**
     * Frames per second.
     */
    private final static int FPS = 30;
    /**
     * Size of the game screen.
     */
    private final static Dimension GAME_PANEL_SIZE = new Dimension(800, 600);
    /**
     * The number of bits passed to the capabilities.
     */
    private final static int CAP_BITS = 16;

    /**
     * Game timer.
     */
    private Timer gameTimer;
    /**
     * Game screen panel.
     */
    private GameContainer gameContainer;
     /**
     * The cockpit panel.
     */
    private JPanel cockpitPanel;
    /**
     * The message label.
     */
    private JLabel messageLabel;
    /**
     * The misc panel.
     */
    private JPanel miscPanel;
    /**
     * Squadron.
     */
    private Squadron selectedSquadron;
    /**
     * Stage.
     */
    private Stage stage;
    /**
     * The controller.
     */
    private PlayerController controller;
    /**
     * The game camera.
     */
    private Camera camera;
    /**
     * The gear to change when the stage time is over.
     */
    private Gear gear;
    /**
     * Airplanes of the stage.
     */
    private List<Airplane> airplanes;
    /**
     * The terrain.
     */
    private Terrain terrain;

    /**
     * @param gearListener The gear listener.
     * @param gear The gear to change when the stage is over.
     * @param selectedSquadron The selected squadron.
     * @param stage The stage.
     */
    public GameGear(GearListener gearListener, Gear gear, Squadron selectedSquadron, Stage stage)
    {
        super(gearListener);
        this.gear = gear;
        this.selectedSquadron = selectedSquadron;
        this.stage = stage;
    }

    /**
     */
    @Override
    public boolean closing()
    {
        changeGear();
        return false;
    }

    /**
     */
    @Override
    public void load()
    {
        airplanes = new ArrayList<Airplane>();

        JWavefrontModel model = wavefrontLoader.get(selectedSquadron.getType().getMapKey()
                + "/Airplane.obj");
        JWavefrontModel shadowModel = wavefrontLoader.get(selectedSquadron.getType().getMapKey()
                + "/AirplaneShadow.obj");
        JWavefrontModel smokeModel = wavefrontLoader.get(selectedSquadron.getType().getMapKey()
                + "/Smoke.obj");

        List<Smoker> smokers = new ArrayList<Smoker>();

        JWavefrontModel propellerModel = wavefrontLoader.get(selectedSquadron.getType().getMapKey()
                    + "/Propeller.obj");

        FlapsData[] flapsDataList = flapsLoader.getObjects().toArray(new FlapsData[0]);
        JWavefrontModel[] flapsModels = new JWavefrontModel[flapsDataList.length];
        int i = 0;
        for (FlapsData flapsData : flapsDataList)
        {
            flapsModels[i++] = wavefrontLoader.get(selectedSquadron.getType().getMapKey()
                    + "/" + flapsData.getModelName() + ".obj");
        }

        List<RouteData> routes = stage.getStageData().getRoutes();
        List<Controller> controllers = new ArrayList<Controller>();
        for (RouteData routeData : routes)
        {
            List<MovementData> movements = new LinkedList<MovementData>(routeData.getMovements());

            Sound3D[] engineSounds = new Sound3D[4];
            engineSounds[0] = new Sound3D(
                    soundLoader.get(selectedSquadron.getType().getMapKey() + "/Engine0.ogg"));
            engineSounds[1] = new Sound3D(
                    soundLoader.get(selectedSquadron.getType().getMapKey() + "/Engine1.ogg"));
            engineSounds[2] = new Sound3D(
                    soundLoader.get(selectedSquadron.getType().getMapKey() + "/Engine2.ogg"));
            engineSounds[3] = new Sound3D(
                    soundLoader.get(selectedSquadron.getType().getMapKey() + "/Engine3.ogg"));

            Airplane airplane = new Airplane(model, shadowModel, flapsModels, propellerModel, flapsDataList,
                    new Point3f(routeData.getStartPosition()),
                    selectedSquadron.getSquadronData().getAirplaneData(), 
                    new Vector3f(routeData.getStartSpeed()),
                    routeData.isInverted(),
                    /*controllers.isEmpty() ? new Sound3D[] { } : */engineSounds);

            Smoker smoker = new Smoker(smokeModel, airplane);
            smokers.add(smoker);
            
            airplanes.add(airplane);

            Sound3D windSound = new Sound3D(
                    soundLoader.get(selectedSquadron.getType().getMapKey() + "/Wind.ogg"));

            Controller newController;
            if (controllers.isEmpty())
            {
                camera = new Camera(airplane, windSound);
                controller = new PlayerController(camera, airplane, GAME_PANEL_SIZE, movements);
                newController = controller;
            }
            else
                newController = new AutomaticController(airplane, movements);

            controllers.add(newController);
        }

        terrain = new Terrain(wavefrontLoader.get(selectedSquadron.getType().getMapKey()
                + "/Terrain.obj"), new ArrayList<GameObject>(airplanes), 
                new Point3f(0.0f, Integer.MAX_VALUE, 0.0f), Camera.SIGHT);

        Sky sky = new Sky(wavefrontLoader.get(selectedSquadron.getType().getMapKey()
                + "/Sky.obj"), Camera.SIGHT);

        List<GameObject> objects = new ArrayList<GameObject>();
        objects.add(terrain);
        objects.add(sky);
        objects.addAll(1, airplanes);

        GLCapabilities capabilities = new GLCapabilities(GLProfile.getDefault());
        capabilities.setAccumBlueBits(CAP_BITS);
        capabilities.setAccumGreenBits(CAP_BITS);
        capabilities.setAccumRedBits(CAP_BITS);
        capabilities.setDoubleBuffered(true);
        capabilities.setHardwareAccelerated(true);

        List<RadioSound> radioSounds = new ArrayList<RadioSound>();
        for (RadioData radioData : stage.getStageData().getRadios())
        {
            radioSounds.add(new RadioSound(
                    soundLoader.get(selectedSquadron.getType().getMapKey() + "/Radio" + radioData.getIndex() + ".ogg"),
                    radioData.getTime()));
        }

        gameContainer = new GameContainer(capabilities, GAME_PANEL_SIZE, objects, camera, controllers,
                radioSounds, controller, stage, smokers)
        {
            @Override
            protected void paintComponent(Graphics g)
            {
                super.paintComponent(g);
                if (!camera.isSpectator())
                    controller.paintMovement(g);
            }
        };

        cockpitPanel = new JPanel()
        {
            @Override
            protected void paintComponent(Graphics g)
            {
                if (!GameGear.this.camera.isSpectator())
                {
                    super.paintComponent(g);
                }
            }
        };
        cockpitPanel.setOpaque(false);
        GameGear.this.camera.getGameObject().setupInfoPanel(cockpitPanel);

        messageLabel = new ALabel("Focus your joystick on the center and press any key to start. "
                + "Press ESC at any time to return to the main menu.", Color.WHITE, Color.BLACK);

        miscPanel = new JPanel();
        miscPanel.setOpaque(false);
        miscPanel.setLayout(new GridBagLayout());

        GridBagConstraints c = new GridBagConstraints();

        c.gridx = 0;
        c.gridy = 0;
        camera.createInfoPanel(miscPanel, c);

        c.gridy++;
        controller.createInfoPanel(miscPanel, c);

        c.gridy++;
        stage.createInfoPanel(miscPanel, c);
        
        addKeyListener(new KeyAdapter()
        {
            @Override
            public void keyPressed(KeyEvent e)
            {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
                {
                    changeGear();
                }
                else if (e.getKeyCode() == KeyEvent.VK_R && gameContainer.isStarted())
                {
                    replayStage();
                }
                else if (!gameContainer.isStarted())
                {
                    try
                    {
                        gameContainer.setStarted();
                        messageLabel.setText("");
                    }
                    catch (LineUnavailableException ex)
                    {
                        Thread.getDefaultUncaughtExceptionHandler().uncaughtException(null, ex);
                    }
                }
            }
        });

        addKeyListener(controller);
        if (!controller.setupJoystick())
            addMouseMotionListener(controller);

        gameTimer = new Timer(1000 / FPS, GameGear.this);
        gameTimer.start();
        
        c = new GridBagConstraints();

        c.gridx = 0;
        c.gridy = 0;
        add(gameContainer, c);
        
        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 1.0f;
        c.gridheight = 2;
        c.insets = new Insets(10, 0, 0, 0);
        gameContainer.add(cockpitPanel, c);

        c.gridy++;
        c.weightx = 0.0f;
        c.anchor = GridBagConstraints.NORTHEAST;
        c.gridheight = 1;
        gameContainer.add(miscPanel, c);

        c.gridx = 0;
        c.gridwidth = 2;
        c.weighty = 1.0f;
        c.anchor = GridBagConstraints.NORTH;
        c.gridy++;
        gameContainer.add(messageLabel, c);
  
        stage.start(true);
    }

    /**
     */
    public void actionPerformed(ActionEvent e)
    {
        if (e.getSource() == gameTimer)
        {
            if (stage.getRemainingTime() <= 0)
            {
                finishStage("Time is over! Press ESC key to return or R to replay.");
            }
            else
            {
                Airplane airplane = controller.getAirplane();

                if (airplane.isOutOfArea())
                {
                    finishStage("You passed out the flight area! Press ESC key to return.");
                    gameContainer.setFreezed(true);
                }
                else if (airplane.isCollidingWith(terrain))
                {
                    finishStage("You were about to collide with the ground! Press ESC key to return.");
                    gameContainer.setFreezed(true);
                }
                else
                {
                    for (Airplane a : airplanes)
                    {
                        if (a == airplane)
                            continue;
                        if (airplane.isCollidingWith(a))
                        {
                            finishStage("You were about to collide with another airplane! Press ESC key to return.");
                            gameContainer.setFreezed(true);
                        }
                    }
                }
            }
            // This will trigger the main loop method of the game later.
            gameContainer.repaint();
        }
    }

    /**
     * Finishes the stage.
     * @param message The message to be shown.
     */
    private void finishStage(String message)
    {
        gameTimer.stop();
        messageLabel.setText(message);
        removeKeyListener(controller);
        removeMouseMotionListener(controller);
        controller.blockJoystick();
        gameContainer.stopSounds();
    }

    /**
     * Starts the replay.
     */
    private void replayStage()
    {
        if (stage.getRemainingTime() > 0)
            return;

        stage.setScoreRecord(Math.max(stage.getScore(), stage.getScoreRecord()));
        
        List<List<GhostData>> airplanesGhosts = new ArrayList<List<GhostData>>();
        for (Airplane airplane : airplanes)
        {
            airplanesGhosts.add(airplane.getGhosts());
        }

        Gear replayGear = new ReplayGear(gearListener, gear, selectedSquadron, stage, airplanesGhosts);
        replayGear.load();
        gearListener.gearChanging(replayGear);
    }
    
    /**
     * Changes this context to the new gear context.
     */
    private void changeGear()
    {
        stage.setScoreRecord(Math.max(stage.getScore(), stage.getScoreRecord()));

        // In case the player presses ESC, the joystick wont be removed. Does this here.
        controller.blockJoystick();

        gameContainer.stopSounds();
        gameTimer.stop();
        gear.load();
        gearListener.gearChanging(gear);
    }
}
